using System;
using System.Linq;
using System.Collections;
using System.Collections.Generic;

namespace ZFramework
{
    public struct Vec3 : IEnumerable<float>, IEquatable<Vec3>
    {
        public float x, y, z;

        public float this[int i]
        {
            readonly get => i switch
            {
                0 => x,
                1 => y,
                2 => z,
                _ => throw new IndexOutOfRangeException(),
            };
            set
            {
                switch(i)
                {
                    case 0:
                        x = value;
                        break;
                    case 1:
                        y = value;
                        break;
                    case 2:
                        z = value;
                        break;
                }
            }
        }

        public Vec3(float x, float y, float z)
        {
            this.x = x;
            this.y = y;
            this.z = z;
        }

        public Vec3(IEnumerable<float> v)
        {
            var it = v.GetEnumerator();
            x = it.MoveNext() ? it.Current : 0.0f;
            y = it.MoveNext() ? it.Current : 0.0f;
            z = it.MoveNext() ? it.Current : 0.0f;
        }

        public readonly float LengthSquared => Dot(this);
        public readonly float Length => MathF.Sqrt(LengthSquared);
        public readonly Vec3 Abs() => new(this.Select(MathF.Abs));
        public readonly float Dot(Vec3 rhs) => x * rhs.x + y * rhs.y + z * rhs.z;
        public readonly Vec3 Cross(Vec3 rhs) => new(y * rhs.z - z * rhs.y, z * rhs.x - x * rhs.z, x * rhs.y - y * rhs.x);
        public readonly Vec3I ToVec3I() => new((int)x, (int)y, (int)z);
        public readonly Vec3 Floor() => new(MathF.Floor(x), MathF.Floor(y), MathF.Floor(z));
        public readonly Vec3 Ceil() => new(MathF.Ceiling(x), MathF.Ceiling(y), MathF.Ceiling(z));
        public readonly Vec3I FloorToInt() => Floor().ToVec3I();
        public readonly Vec3I CeilToInt() => Ceil().ToVec3I();
        public readonly Vec3 Normalized() => this / Length;

        public override readonly string ToString() => $"({x},{y},{z})";
        public readonly IEnumerator<float> GetEnumerator()
        {
            yield return x;
            yield return y;
            yield return z;
        }
        readonly IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
        public readonly bool Equals(Vec3 rhs) => rhs.Zip(this).Aggregate(true, (isEq, v) => isEq && MathF.Abs(v.First - v.Second) < float.Epsilon);
        public override readonly int GetHashCode() => HashCode.Combine(x, y, z);
        public override readonly bool Equals(object obj) => obj is Vec3 v && Equals(v);
        public static Vec3 Zero => new(0, 0, 0);
        public static Vec3 One => new(1, 1, 1);
        public static Vec3 Left => new(-1, 0, 0);
        public static Vec3 Right => new(1, 0, 0);
        public static Vec3 Up => new(0, 1, 0);
        public static Vec3 Down => new(0, -1, 0);
        public static Vec3 Forward => new(0, 0, -1);
        public static Vec3 Back => new(0, 0, 1);

        public static Vec3 operator -(Vec3 v) => new(-v.x, -v.y, -v.z);

        public static Vec3 operator +(Vec3 lhs, Vec3 rhs) => new(lhs.x + rhs.x, lhs.y + rhs.y, lhs.z + rhs.z);
        public static Vec3 operator -(Vec3 lhs, Vec3 rhs) => new(lhs.x - rhs.y, lhs.y - rhs.y, lhs.z - rhs.z);
        public static Vec3 operator *(Vec3 lhs, Vec3 rhs) => new(lhs.x * rhs.x, lhs.y * rhs.y, lhs.z * rhs.z);
        public static Vec3 operator /(Vec3 lhs, Vec3 rhs) => new(lhs.x / rhs.x, lhs.y / rhs.y, lhs.z / rhs.z);

        public static Vec3 operator *(Vec3 lhs, float rhs) => new(lhs.x * rhs, lhs.y * rhs, lhs.z * rhs);
        public static Vec3 operator *(float lhs, Vec3 rhs) => rhs * lhs;
        public static Vec3 operator /(Vec3 lhs, float rhs) => new(lhs.x / rhs, lhs.y / rhs, lhs.z / rhs);

        public static bool operator ==(Vec3 lhs, Vec3 rhs) => lhs.Equals(rhs);
        public static bool operator !=(Vec3 lhs, Vec3 rhs) => !lhs.Equals(rhs);

#if GODOT4
        public static implicit operator Godot.Vector3(Vec3 v) => new(v.x, v.y, v.z);
        public static implicit operator Vec3(Godot.Vector3 v) => new(v.X, v.Y, v.Z);
#endif
    }
}